package evl

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"gitcode.com/deyiyangyang/bampoo/ast"
	"gitcode.com/deyiyangyang/bampoo/obj"
)

// TestBuiltinNewline tests the newline builtin function
func TestBuiltinNewline(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test newline with no arguments
	newlineCall := &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "newline"},
		},
	}

	result, err := evaluator.Eval(newlineCall)
	if err != nil {
		t.Fatalf("unexpected error calling newline: %v", err)
	}

	// Should return nil
	if _, ok := result.(*obj.NilValue); !ok {
		t.Fatalf("expected NilValue, got %T", result)
	}
}

// TestBuiltinNewlineWithArguments tests newline with arguments (should fail)
func TestBuiltinNewlineWithArguments(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test newline with arguments
	newlineCall := &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "newline"},
			&ast.IntegerLiteral{Value: 1},
		},
	}

	_, err := evaluator.Eval(newlineCall)
	if err == nil {
		t.Fatal("expected error when calling newline with arguments, got nil")
	}

	expectedError := "newline: expected 0 arguments, got 1"
	if err.Error() != expectedError {
		t.Fatalf("expected error '%s', got '%s'", expectedError, err.Error())
	}
}

// TestBuiltinInBegin tests builtin functions in begin blocks
func TestBuiltinInBegin(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test newline in begin block
	begin := &ast.BeginExpr{
		Body: []ast.Expression{
			&ast.List{
				Elements: []ast.Expression{
					&ast.Symbol{Value: "newline"},
				},
			},
			&ast.IntegerLiteral{Value: 42},
		},
	}

	result, err := evaluator.Eval(begin)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Should return the last expression (42)
	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 42 {
		t.Errorf("expected 42, got %d", intVal.Value)
	}
}

// TestBuiltinLoad tests the load builtin function with a valid file
func TestBuiltinLoad(t *testing.T) {
	// Create a temporary directory
	td, err := os.MkdirTemp("", "load-test")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(td) // Clean up

	// Create a test Scheme file
	testFile := filepath.Join(td, "test.scm")
	testContent := "(+ 1 2)\n(* 3 4)\n"
	if err := ioutil.WriteFile(testFile, []byte(testContent), 0644); err != nil {
		t.Fatalf("failed to write test file: %v", err)
	}

	// Set up environment and evaluator
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test loading the file
	loadCall := &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "load"},
			&ast.StringLiteral{Value: testFile},
		},
	}

	result, err := evaluator.Eval(loadCall)
	if err != nil {
		t.Fatalf("unexpected error calling load: %v", err)
	}

	// Should return the result of the last expression in the file (3*4=12)
	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 12 {
		t.Errorf("expected 12, got %d", intVal.Value)
	}
}

// TestBuiltinLoadFileDoesNotExist tests load with a non-existent file
func TestBuiltinLoadFileDoesNotExist(t *testing.T) {
	env := obj.NewEnvironment()	
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test loading a non-existent file
	loadCall := &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "load"},
			&ast.StringLiteral{Value: "/path/to/non/existent/file.scm"},
		},
	}

	_, err := evaluator.Eval(loadCall)
	if err == nil {
		t.Fatal("expected error when calling load with non-existent file, got nil")
	}

	// Just check that an error is returned (exact message may vary)
	if err.Error() == "" {
		t.Fatal("expected non-empty error message")
	}
}

// TestBuiltinNumberP tests the number? builtin function
func TestBuiltinNumberP(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Define some symbols in the environment for testing
	env.Set("symbol", &obj.SymbolValue{Name: "symbol"})

	// Test with integer (should be true)
	testCases := []struct {
		name     string
		input    ast.Expression
		expected bool
	}{{
		name: "integer is number",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "number?"},
				&ast.IntegerLiteral{Value: 123},
			},
		},
		expected: true,
	}, {
		name: "string is not number",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "number?"},
				&ast.StringLiteral{Value: "hello"},
			},
		},
		expected: false,
	}, {
		name: "boolean is not number",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "number?"},
				&ast.BooleanLiteral{Value: true},
			},
		},
		expected: false,
	}, {
		name: "symbol is not number",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "number?"},
				&ast.Symbol{Value: "symbol"},
			},
		},
		expected: false,
	}, {
		name: "null is not number",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "number?"},
				&ast.List{Elements: []ast.Expression{}},
			},
		},
		expected: false,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := evaluator.Eval(tc.input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tc.expected {
				t.Errorf("expected %v, got %v", tc.expected, boolVal.Value)
			}
		})
	}

	// Test with wrong number of arguments
	wrongArgs := &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "number?"},
			&ast.IntegerLiteral{Value: 1},
			&ast.IntegerLiteral{Value: 2},
		},
	}

	_, err := evaluator.Eval(wrongArgs)
	if err == nil {
		t.Fatal("expected error when calling number? with wrong argument count, got nil")
	}
}

// TestBuiltinRealP tests the real? builtin function
func TestBuiltinRealP(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test with integer (should be true)
	testCases := []struct {
		name     string
		input    ast.Expression
		expected bool
	}{{
		name: "integer is real",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "real?"},
				&ast.IntegerLiteral{Value: 123},
			},
		},
		expected: true,
	}, {
		name: "string is not real",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "real?"},
				&ast.StringLiteral{Value: "hello"},
			},
		},
		expected: false,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := evaluator.Eval(tc.input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tc.expected {
				t.Errorf("expected %v, got %v", tc.expected, boolVal.Value)
			}
		})
	}
}

// TestBuiltinRationalP tests the rational? builtin function
func TestBuiltinRationalP(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test with integer (should be true)
	testCases := []struct {
		name     string
		input    ast.Expression
		expected bool
	}{{
		name: "integer is rational",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "rational?"},
				&ast.IntegerLiteral{Value: 123},
			},
		},
		expected: true,
	}, {
		name: "string is not rational",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "rational?"},
				&ast.StringLiteral{Value: "hello"},
			},
		},
		expected: false,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := evaluator.Eval(tc.input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tc.expected {
				t.Errorf("expected %v, got %v", tc.expected, boolVal.Value)
			}
		})
	}
}

// TestBuiltinIntegerP tests the integer? builtin function
func TestBuiltinIntegerP(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test with integer (should be true)
	testCases := []struct {
		name     string
		input    ast.Expression
		expected bool
	}{{
		name: "integer is integer",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "integer?"},
				&ast.IntegerLiteral{Value: 123},
			},
		},
		expected: true,
	}, {
		name: "string is not integer",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "integer?"},
				&ast.StringLiteral{Value: "hello"},
			},
		},
		expected: false,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := evaluator.Eval(tc.input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tc.expected {
				t.Errorf("expected %v, got %v", tc.expected, boolVal.Value)
			}
		})
	}
}

// TestBuiltinSymbolP tests the symbol? builtin function
func TestBuiltinSymbolP(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Define some symbols in the environment for testing
	env.Set("test-symbol", &obj.SymbolValue{Name: "test-symbol"})
	env.Set("hello", &obj.SymbolValue{Name: "hello"})

	// Test with symbol (should be true)
	testCases := []struct {
		name     string
		input    ast.Expression
		expected bool
	}{{
		name: "symbol is symbol",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "symbol?"},
				&ast.Symbol{Value: "test-symbol"},
			},
		},
		expected: true,
	}, {
		name: "string is not symbol",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "symbol?"},
				&ast.StringLiteral{Value: "test-symbol"},
			},
		},
		expected: false,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := evaluator.Eval(tc.input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tc.expected {
				t.Errorf("expected %v, got %v", tc.expected, boolVal.Value)
			}
		})
	}
}

// TestBuiltinStringP tests the string? builtin function
func TestBuiltinStringP(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Define some symbols in the environment for testing
	env.Set("hello", &obj.SymbolValue{Name: "hello"})

	// Test with string (should be true)
	testCases := []struct {
		name     string
		input    ast.Expression
		expected bool
	}{{
		name: "string is string",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "string?"},
				&ast.StringLiteral{Value: "hello"},
			},
		},
		expected: true,
	}, {
		name: "symbol is not string",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "string?"},
				&ast.Symbol{Value: "hello"},
			},
		},
		expected: false,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := evaluator.Eval(tc.input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tc.expected {
				t.Errorf("expected %v, got %v", tc.expected, boolVal.Value)
			}
		})
	}
}

// TestBuiltinBooleanP tests the boolean? builtin function
func TestBuiltinBooleanP(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test with boolean (should be true)
	testCases := []struct {
		name     string
		input    ast.Expression
		expected bool
	}{{
		name: "true is boolean",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "boolean?"},
				&ast.BooleanLiteral{Value: true},
			},
		},
		expected: true,
	}, {
		name: "false is boolean",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "boolean?"},
				&ast.BooleanLiteral{Value: false},
			},
		},
		expected: true,
	}, {
		name: "integer is not boolean",
		input: &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "boolean?"},
				&ast.IntegerLiteral{Value: 1},
			},
		},
		expected: false,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result, err := evaluator.Eval(tc.input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tc.expected {
				t.Errorf("expected %v, got %v", tc.expected, boolVal.Value)
			}
		})
	}
}

// TestBuiltinLoadWrongArgumentCount tests load with wrong number of arguments
func TestBuiltinLoadWrongArgumentCount(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test load with no arguments
	loadCall := &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "load"},
		},
	}

	_, err := evaluator.Eval(loadCall)
	if err == nil {
		t.Fatal("expected error when calling load with no arguments, got nil")
	}

	expectedError := "load: expected 1 argument, got 0"
	if err.Error() != expectedError {
		t.Fatalf("expected error '%s', got '%s'", expectedError, err.Error())
	}

	// Test load with too many arguments
	loadCall = &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "load"},
			&ast.StringLiteral{Value: "file.scm"},
			&ast.IntegerLiteral{Value: 1},
		},
	}

	_, err = evaluator.Eval(loadCall)
	if err == nil {
		t.Fatal("expected error when calling load with too many arguments, got nil")
	}

	expectedError = "load: expected 1 argument, got 2"
	if err.Error() != expectedError {
		t.Fatalf("expected error '%s', got '%s'", expectedError, err.Error())
	}
}

// TestBuiltinLoadWrongArgumentType tests load with wrong argument type
func TestBuiltinLoadWrongArgumentType(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test load with integer argument instead of string
	loadCall := &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "load"},
			&ast.IntegerLiteral{Value: 123},
		},
	}

	_, err := evaluator.Eval(loadCall)
	if err == nil {
		t.Fatal("expected error when calling load with wrong argument type, got nil")
	}

	expectedError := "load: expected string argument, got integer"
	if err.Error() != expectedError {
		t.Fatalf("expected error '%s', got '%s'", expectedError, err.Error())
	}
}

// TestBuiltinLoadSyntaxError tests load with a file containing syntax errors
func TestBuiltinLoadSyntaxError(t *testing.T) {
	// Create a temporary directory
	td, err := os.MkdirTemp("", "load-test-syntax")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(td) // Clean up

	// Create a test Scheme file with syntax errors
	testFile := filepath.Join(td, "syntax-error.scm")
	testContent := "(+ 1 2\n" // Missing closing parenthesis
	if err := ioutil.WriteFile(testFile, []byte(testContent), 0644); err != nil {
		t.Fatalf("failed to write test file: %v", err)
	}

	env := obj.NewEnvironment()
	evaluator := New(env)

	// Add builtin functions
	builtins := CreateBuiltinFunctions()
	for name, builtin := range builtins {
		env.Set(name, builtin)
	}

	// Test loading the file with syntax errors
	loadCall := &ast.List{
		Elements: []ast.Expression{
			&ast.Symbol{Value: "load"},
			&ast.StringLiteral{Value: testFile},
		},
	}

	_, err = evaluator.Eval(loadCall)
	if err == nil {
		t.Fatal("expected error when calling load with syntax error file, got nil")
	}

	// Just check that an error is returned (exact message may vary)
	if err.Error() == "" {
		t.Fatal("expected non-empty error message")
	}
}